use itertools::Itertools;

use std::fs;
use std::io::{Read, Write};
use std::str;

const PHF_SRC: &str = "\
// A stripped down `phf` crate fork.
//
// https://github.com/sfackler/rust-phf

struct Map<V: 'static> {
    pub key: u64,
    pub disps: &'static [(u32, u32)],
    pub entries: &'static[(&'static str, V)],
}

impl<V: PartialEq> Map<V> {
    fn get(&self, key: &str) -> Option<&V> {
        use std::borrow::Borrow;

        let hash = hash(key, self.key);
        let index = get_index(hash, self.disps, self.entries.len());
        let entry = &self.entries[index as usize];
        let b = entry.0.borrow();
        if b == key {
            Some(&entry.1)
        } else {
            None
        }
    }

    fn key(&self, value: &V) -> &'static str {
        self.entries.iter().find(|kv| kv.1 == *value).unwrap().0
    }
}

#[inline]
fn hash(x: &str, key: u64) -> u64 {
    use std::hash::Hasher;

    let mut hasher = siphasher::sip::SipHasher13::new_with_keys(0, key);
    hasher.write(x.as_bytes());
    hasher.finish()
}

#[inline]
fn get_index(hash: u64, disps: &[(u32, u32)], len: usize) -> u32 {
    let (g, f1, f2) = split(hash);
    let (d1, d2) = disps[(g % (disps.len() as u32)) as usize];
    displace(f1, f2, d1, d2) % (len as u32)
}

#[inline]
fn split(hash: u64) -> (u32, u32, u32) {
    const BITS: u32 = 21;
    const MASK: u64 = (1 << BITS) - 1;

    ((hash & MASK) as u32,
     ((hash >> BITS) & MASK) as u32,
     ((hash >> (2 * BITS)) & MASK) as u32)
}

#[inline]
fn displace(f1: u32, f2: u32, d1: u32, d2: u32) -> u32 {
    d2 + f1 * d1 + f2
}";

fn main() {
    if let Err(e) = gen() {
        println!("{:?}", e);
        std::process::exit(1);
    }
}

fn gen() -> Result<(), Box<dyn std::error::Error>> {
    let f = &mut fs::File::create("../src/svgtree/names.rs")?;

    writeln!(f, "// This file is autogenerated. Do not edit it!")?;
    writeln!(f, "// See ./codegen for details.\n")?;

    gen_map("elements.txt", "An element ID.", "EId", "ELEMENTS", f)?;

    gen_map("attributes.txt", "An attribute ID.", "AId", "ATTRIBUTES", f)?;

    writeln!(f, "{}", PHF_SRC)?;

    Ok(())
}

fn gen_map(
    spec_path: &str,
    enum_docs: &str,
    enum_name: &str,
    map_name: &str,
    f: &mut fs::File,
) -> Result<(), Box<dyn std::error::Error>> {
    let mut spec = String::new();
    fs::File::open(spec_path)?.read_to_string(&mut spec)?;

    let names: Vec<&str> = spec.split('\n').filter(|s| !s.is_empty()).collect();

    let joined_names = names.iter().map(|n| to_enum_name(n)).join(",\n    ");

    let mut map = phf_codegen::Map::new();
    for name in &names {
        map.entry(*name, &format!("{}::{}", enum_name, to_enum_name(name)));
    }

    let mut map_data = Vec::new();
    map.build(&mut map_data)?;
    let map_data = String::from_utf8(map_data)?;
    let map_data = map_data.replace("::phf::Map", "Map");
    let map_data = map_data.replace("::phf::Slice::Static(", "");
    let map_data = map_data.replace("]),", "],");

    writeln!(f, "/// {}", enum_docs)?;
    writeln!(f, "#[allow(missing_docs)]")?;
    writeln!(f, "#[derive(Clone, Copy, PartialEq)]")?;
    writeln!(f, "pub enum {} {{", enum_name)?;
    writeln!(f, "    {}", joined_names)?;
    writeln!(f, "}}\n")?;

    writeln!(
        f,
        "static {}: Map<{}> = {};\n",
        map_name, enum_name, map_data
    )?;

    writeln!(f, "impl {} {{", enum_name)?;
    writeln!(
        f,
        "    pub(crate) fn from_str(text: &str) -> Option<{}> {{",
        enum_name
    )?;
    writeln!(f, "        {}.get(text).cloned()", map_name)?;
    writeln!(f, "    }}")?;
    writeln!(f, "")?;
    writeln!(f, "    /// Returns the original string.")?;
    writeln!(f, "    #[inline(never)]")?;
    writeln!(f, "    pub fn to_str(self) -> &'static str {{")?;
    writeln!(f, "        {}.key(&self)", map_name)?;
    writeln!(f, "    }}")?;
    writeln!(f, "}}\n")?;

    writeln!(f, "impl std::fmt::Debug for {} {{", enum_name)?;
    writeln!(
        f,
        "    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {{"
    )?;
    writeln!(f, "        write!(f, \"{{}}\", self.to_str())")?;
    writeln!(f, "    }}")?;
    writeln!(f, "}}\n")?;

    writeln!(f, "impl std::fmt::Display for {} {{", enum_name)?;
    writeln!(
        f,
        "    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {{"
    )?;
    writeln!(f, "        write!(f, \"{{:?}}\", self)")?;
    writeln!(f, "    }}")?;
    writeln!(f, "}}")?;
    writeln!(f, "")?;

    Ok(())
}

// some-string -> SomeString
// some_string -> SomeString
// some:string -> SomeString
// 100 -> N100
fn to_enum_name(name: &str) -> String {
    let mut change_case = false;
    let mut s = String::with_capacity(name.len());
    for (idx, c) in name.chars().enumerate() {
        if idx == 0 {
            if c.is_digit(10) {
                s.push('N');
                s.push(c);
            } else {
                s.push(c.to_uppercase().next().unwrap());
            }

            continue;
        }

        if c == '-' || c == '_' || c == ':' {
            change_case = true;
            continue;
        }

        if change_case {
            s.push(c.to_uppercase().next().unwrap());
            change_case = false;
        } else {
            s.push(c);
        }
    }

    s
}
